package com.qire.antsbinder.viewModel;

import com.qire.antsbinder.DynamicProxyFactory;
import com.qire.antsbinder.DynamicProxyFactory.ProxyHandler;
import com.qire.antsbinder.utils.TypeUtils;
import com.qire.antsbinder.viewModel.annotation.ViewModelProperty;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;

/**
 * 实现了代理扩展的ViewModel类，允许用过接口来扩展绑定到布局的控件的关联行为。
 * @param <P> 扩展代理的接口类型，代理方法使用{@link ViewModelProperty ViewModelProperty}来描述。
 * @see ViewModelProperty
 */
public class AntsViewModel<P> extends ViewModel implements ProxyHandler {

    private final DynamicProxyFactory dynamicProxyFactory = new DynamicProxyFactory(this);
    private final Map<String, MutableLiveData<?>> propertyMap = new HashMap<>();

    private P proxy;

    /**
     * 以set的方式修改属性值，set会在当前线程调用
     * @param propertyName  属性名称
     * @param data          属性值
     * @param <D>           属性值类型
     */
    public <D> void set(String propertyName,D data) {
        MutableLiveData<D> liveData = this.<D>get(propertyName);
        liveData.setValue(data);
    }

    /**
     * 以post的方式修改了属性值，post永远会在主线程中调用
     * @param propertyName  属性名称
     * @param data          属性值
     * @param <D>           属性值类型
     */
    public <D> void put(String propertyName,D data) {
        MutableLiveData<D> liveData = this.<D>get(propertyName);
        liveData.postValue(data);
    }

    /**
     * 通过属性名获取属性值
     * @param propertyName  属性名称
     * @param <D>           属性值类型
     * @return              返回一个持有泛型类型的liveData
     */
    public synchronized <D> MutableLiveData<D> get(String propertyName) {
        MutableLiveData<D> data = (MutableLiveData<D>) propertyMap.get(propertyName);
        if(data == null) {
            data = new MutableLiveData<D>();
            propertyMap.put(propertyName, data);
        }
        return data;
    }

    /**
     * 通过属性名称获得具体属性真实值
     * @param propertyName  属性名称
     * @param <D>           返回值类型泛型
     * @return              属性值
     */
    public <D> D getToValue(String propertyName) {
        return getToValue(propertyName, null);
    }

    /**
     * 通过属性名称获得具体属性真实值,如果属性不存在则返回 {@code defValue}
     * @param propertyName  属性名称
     * @param defValue      默认属性值
     * @param <D>           返回值类型泛型
     * @return              属性值
     */
    public <D> D getToValue(String propertyName, D defValue) {
        D propertyData = this.<D>get(propertyName).getValue();
        return propertyData == null ? defValue : propertyData;
    }

    /**
     * 通过属性名称为对应属性设置一个永久观察者对象，在属性发生改变时会通知该观察者，且不会受到Activity生命周期影响
     * @param propertyName  属性名称
     * @param observer      观察者
     * @param <D>           属性类型
     */
    public <D> void observeForever(@NonNull String propertyName, @NonNull Observer<? super D> observer) {
        this.<D>get(propertyName).observeForever(observer);
    }

    /**
     * 通过属性名称为对应属性设置一个观察者对象，在属性发生改变时会通知该观察者，该观察者会受到Activity生命周期影响
     * @param propertyName  属性名称
     * @param owner         生命周期对象
     * @param observer      观察者
     * @param <D>           属性值类型
     */
    public <D> void observe(@NonNull String propertyName, @NonNull LifecycleOwner owner, @NonNull Observer<? super D> observer) {
        this.<D>get(propertyName).observe(owner,observer);
    }

    /**
     * 移除属性名称对应属性上的一个观察者
     * @param propertyName  属性名称
     * @param observer      观察者
     * @param <D>           属性值类型
     */
    public <D> void removeObserver(@NonNull String propertyName, @NonNull final Observer<? super D> observer) {
        this.<D>get(propertyName).removeObserver(observer);
    }

    /**
     * 移除属性名称对应属性上的生命周期内所有的观察者
     * @param propertyName  属性名称
     * @param owner         生命周期对象
     * @param <D>           属性值类型
     */
    public <D> void removeObservers(@NonNull String propertyName, @NonNull final LifecycleOwner owner) {
        this.<D>get(propertyName).removeObservers(owner);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        proxy = null;
    }

    /**
     * 获得代理实体，如果存在则返回，如果不存在则构建。
     * @return 代理实体
     */
    public P proxy() {
        return proxy == null ? buildProxy() : proxy;
    }

    /**
     * 通过类描述泛型来自构建一个代理，如果代理存在则返回存在的代理不在构建.<br />
     * 虽然改方法会判定代理是否存在但是每次获取类型泛型的行为必然不可减少固方法并非O(1)且存在反射调用
     * @return 补全扩展行为的动态代理对象
     */
    public P buildProxy(){
        return buildProxy((Class<P>) TypeUtils.getSuperclassTypeParameter(getClass(),0));
    }

    /**
     * 更具指定类型接口来构建一个代理.<br />
     * 该方法需要指定一个接口Class，如果代理存在且与指定接口类型相同则返回代理，否则重新构建代理。
     * 缓存代理类始终只保存第一次构建的类型，改方法是线程安全的。
     * @param subclass  指定接口的类型
     * @param <I>       接口类型泛型
     * @return          返回接口类型相同的代理类
     */
    public synchronized <I> I buildProxy(Class<I> subclass){
        if(null == proxy) {
            proxy = (P) dynamicProxyFactory.createProxy(subclass);
        } else if (!subclass.isInstance(proxy)) {
            return dynamicProxyFactory.createProxy(subclass);
        }
        return (I) proxy;
    }

    // 代理扩展行为
    @Override
    public boolean parse(Object proxy, Method method, Object[] args) {
        return method.isAnnotationPresent(ViewModelProperty.class);
    }
    @Override
    public <M> M doHandle(Object proxy, Method method, Object[] args) {

        ViewModelProperty property = method.getAnnotation(ViewModelProperty.class);
        String[] propertyNames = property.name();
        if(propertyNames == null || propertyNames.length == 0) {
            throw new RuntimeException("ViewModel Proxy error: " + method.getDeclaringClass() + " 中方法 " + method.getName() + " 注解名称列表不能为空！");
        }

        switch (property.type()) {
            case GET:
                return (M) get(propertyNames[0]);
            case SET:
                if(args == null) {
                    throw new RuntimeException("ViewModel Proxy Set error: " + method.getDeclaringClass() + " 中方法 " + method.getName() + " 必须至少提供一个参数！");
                }
                if(propertyNames.length != args.length) {
                    throw new RuntimeException("ViewModel Proxy Set error: " + method.getDeclaringClass() + "中方法" + method.getName() + " 注解名称与参数数量不匹配！");
                }
                for(int index = 0; index < propertyNames.length; index++) {
                    put(propertyNames[index],args[index]);
                }
                break;
        }
        return null;
    }
    @Override
    public void doBefore(Object proxy, Method method, Object[] args) {}
    @Override
    public void doAfter(Object proxy, Method method, Object[] args) {}

}
